package picturemouse;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * PictureMouse is the base class for the PictureMouse Cinemas booking system.
 * 
 * 
 * @author Group D
 */
public class PictureMouse implements Serializable {
    
    private Film[] films = new Film[0];
    private Screening[] screenings = new Screening[0];
    private Screen[] screens = new Screen[0];
    private Seat[] seats = new Seat[0];
    private Booking[] bookings = new Booking[0];
    private Review[] reviews = new Review[0];
    private Account[] accounts = new Account[0];
    private Newsletter[] newsletters = new Newsletter[0];
    
    public Account currentAccount;

    /**
     * @param args the command line arguments
     * @throws java.io.FileNotFoundException
     * @throws java.lang.ClassNotFoundException
     */
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        
        PictureMouse server = new PictureMouse();
                
        try {
          //Load data from previous sessions
          server.loadData("PictureMouseData");
        } catch (FileNotFoundException err) {}
        
        try {
            
            //Create some screens
            server.addScreen(1);
            server.addScreen(2);
            server.addScreen(3);

            //Create some seats
            for (int screenNum = 1; screenNum<=3; screenNum++) {
                for (int seatNum = 1; seatNum<=10; seatNum++) {
                     server.addSeat("A", seatNum, screenNum); //Seat A
                     server.addSeat("B", seatNum, screenNum); //Seat B
                     server.addSeat("C", seatNum, screenNum); //Seat C
                     server.addSeat("D", seatNum, screenNum); //Seat D
                     server.addSeat("E", seatNum, screenNum); //Seat E
                     server.addSeat("F", seatNum, screenNum); //Seat F
                     server.addSeat("G", seatNum, screenNum); //Seat G
                }
            }
            
        } catch (ScreenAlreadyExistsException | SeatAlreadyExistsException err) {}
        
         
        //Start by showing the login form
        new Login(server);
        
    }
    
    /**
    * Gets a film object by looking up the film's title.
    * 
    * @param	filmName	The name of the film to look up
    * @return				The requested film object
    * @throws				FilmNotFoundException
    */
    public Film getFilmByName(String filmName) throws FilmNotFoundException {
        //Loop the films
        for (Film film : films) {
            if (film.getName().equals(filmName)) {
                //If the film name matches the desired name, return the film object
                return film;
            }
        }
        //If it gets here, the film name wasn't matched, so throw an exception
        throw new FilmNotFoundException();
    }

    /**
    * Creates an array of seats that exist in a given screen.
    * 
    * @param	screenNum	The screen to find seats in
    * @return				Array of seat IDs matching screen number
    */
    public Integer[] getSeatsByScreenNum(int screenNum) {
        ArrayList<Integer> seatList = new ArrayList<>();
        //Loop the seats
        for (Seat seat : seats) {
            if (seat.getScreenNum()==screenNum) {
                //If the seat ID matches the desired ID, add the seat to the list
                seatList.add(seat.getID());
            }
        }
        //Return the list as an array of Integers
        return seatList.toArray(new Integer[0]);
    }
    
    /**
    * Picks a screen object out using the screen ID number.
    * 
    * @param	screenNum	ID number of the required screen
    * @return				The requested screen object
    * @throws				ScreenNotFoundException
    */
    public Screen getScreenByNum(int screenNum) throws ScreenNotFoundException {
        //Loop the screens
        for (Screen screen : screens) {
            if (screen.getScreenNum()==screenNum) {
                //If the screen number matches the desired number, return the screen object
                return screen;
            }
        }
        //If it get's here, the screen number wasn't matched, so throw an exception
        throw new ScreenNotFoundException();
    }

    /**
    * Gets a seat object by looking up the seat's ID number.
    * 
    * @param	seatID		Required seat's ID number
    * @return				The requested seat object
    * @throws				SeatNotFoundException
    */
    public Seat getSeatByID(int seatID) throws SeatNotFoundException {
        //Loop the seats
        for (Seat seat : seats) {
            if (seat.getID()==seatID) {
                //If the seat ID matches the desired ID, return the seat object
                return seat;
            }
        }
        //If it get's here, the seat ID wasn't matched, so throw an exception
        throw new SeatNotFoundException();
    }
    
    /**
    * Gets a seat object by looking up the seat's column and row.
    * 
    * @return				The requested seat object
    * @throws				SeatNotFoundException
    */
    public Seat getSeatByColRow(String seatRow, int seatNum) throws SeatNotFoundException {
        //Loop the seats
        for (Seat seat : seats) {
            if (seat.getNumber()==seatNum && seat.getRow().equals(seatRow)) {
                //If the seat ID matches the desired ID, return the seat object
                return seat;
            }
        }
        //If it get's here, the seat ID wasn't matched, so throw an exception
        throw new SeatNotFoundException();
    }
    
    /**
    * Gets a screening object by looking up the screening ID.
    * 
    * @param	screeningID	Required screening's ID number
    * @return				The requested screening object
    * @throws				ScreeningNotFoundException
    */
    public Screening getScreeningByID(int screeningID) throws ScreeningNotFoundException {
        //Loop the screenings
        for (Screening screening : screenings) {
            if (screening.getID()==screeningID) {
                //If the screening ID matches the desired ID, return the screening object
                return screening;
            }
        }
        //If it get's here, the screening ID wasn't matched, so throw an exception
        throw new ScreeningNotFoundException();
    }
    
    /**
    * Gets a screening object by looking up the screening time and the film name.
    * 
     * @param screeningTime
     * @param filmName
    * @return				The requested screening object
    * @throws				ScreeningNotFoundException
    */
    public Screening getScreeningByTimeAndFilm(String screeningTime, String filmName) throws ScreeningNotFoundException {
        //Loop the screenings
        for (Screening screening : screenings) {
            if (screening.getTime().equals(screeningTime) && screening.getFilmName().equals(filmName)) {
                //If the screening ID matches the desired ID, return the screening object
                return screening;
            }
        }
        //If it get's here, the screening ID wasn't matched, so throw an exception
        throw new ScreeningNotFoundException();
    }
    
    /**
    * Method to return a Booking object from the booking ID    
     * @param bookingID
     * @return Booking object which matches the given bookingID
     * @throws picturemouse.BookingNotFoundException
    */
    public Booking getBookingByID(int bookingID) throws BookingNotFoundException {
        //Loop the bookings
        for (Booking booking : bookings) {
            if (booking.getID()==bookingID) {
                //If the booking ID matches the desired ID, return the booking object
                return booking;
            }
        }
        //If it get's here, the booking ID wasn't matched, so throw an exception
        throw new BookingNotFoundException();
    }

    /**
    * Gets an account object by looking up the customer's name.
    * 
    * @param	customerName	The name of the customer
    * @return					The requested customer object
    * @throws					AccountNotFoundException
    */
    public Account getAccountByName(String customerName) throws AccountNotFoundException {
        //Loop the films
        for (Account account : accounts) {
            if (account.getCustomerName().equals(customerName)) {
                //If the customer name matches the desired name, return the account object
                return account;
            }
        }
        //If it get's here, the film name wasn't matched, so throw an exception
        throw new AccountNotFoundException();
    }
    
    //Get Methods for retrieving the arrays which store the data for the system
    
    public Film[] getFilmArray() {
        return films;
    }
    
    public Screening[] getScreeningArray() {
        return screenings;
    }
    
    public Screen[] getScreenArray() {
        return screens;
    }
    
    public Seat[] getSeatArray() {
        return seats;
    }
    
    public Booking[] getBookingArray() {
        return bookings;
    }
    
    public Review[] getReviewArray() {
    	return reviews;
    }
    
    public Newsletter[] getNewsletterArray() {
    	return newsletters;
    }
    
    public Account[] getAccountArray() {
        return accounts;
    }
    
    
    //Set methods, for adding data to the system
    
    /**
     * Method to check if a film exists by film name.
     * @param filmName
     * @return True if film exists, false if not
     */
    public boolean filmExists(String filmName) {
        try {
            getFilmByName(filmName);
        } catch (FilmNotFoundException e) { //If this exception is thrown, film doesn't exist
            return false;
        }
        return true;
    }
    
    /**
     * Method to add a new film to the system.
     * First checks that the film doesn't already exist in the system.
     * @param filmName
     * @param synopsis
     * @param trailer
     * @throws picturemouse.FilmAlreadyExistsException
     */
    public void addFilm(String filmName, String synopsis, String trailer) throws FilmAlreadyExistsException {
        if (!filmExists(filmName)) {
            //If this error is caught, the film doesn't already exist
            //Add the film to the system
            ArrayList<Film> temp = new ArrayList<>(Arrays.asList(films)); //Convert array to ArrayList for easy adding of new element
            temp.add(new Film(filmName, synopsis, trailer)); //Add the new film element to the ArrayList
            films = temp.toArray(new Film[0]); //Overwrite the films array with the new array
        } else throw new FilmAlreadyExistsException();
    }

    public void removeFilm(Film film) {
        ArrayList<Film> temp = new ArrayList();
        for (Film f : films) {
            if (!f.getName().equals(film.getName())) {
                temp.add(f);
            }
        }
        films = temp.toArray(new Film[0]);
    }

    
    /**
     * Method to add a new seat to the system.
     * First checks that the seat doesn't already exist.
     * @param seatRow
     * @param seatNum
     * @param screenNum
     * @return Returns the seat ID
     * @throws SeatAlreadyExistsException 
     */
    public int addSeat(String seatRow, int seatNum, int screenNum) throws SeatAlreadyExistsException {
        //Loop the seats
        for (int seatID : getSeatsByScreenNum(screenNum)) {
            if (seats[seatID].getRow().equals(seatRow) && seats[seatID].getNumber()==seatNum) {
                //If the seat data matches, throw exception
                throw new SeatAlreadyExistsException();
            }
        }
        //If it gets here, the seat doesn't already exist so add it
        int newSeatIndex = seats.length;
        ArrayList<Seat> temp = new ArrayList<>(Arrays.asList(seats)); //Convert array to ArrayList for easy adding of new element
        temp.add(new Seat(newSeatIndex, seatRow, seatNum, screenNum)); //Add the new seat element to the ArrayList
        seats = temp.toArray(new Seat[0]); //Overwrite the seats array with the new array
        return newSeatIndex;
    }
    
    
    /**
     * Method to add a new screening to the system.
     * First checks the film exists.
     * @param filmName
     * @param time
     * @param screenNum
     * @return Returns the ID of the new screening
     * @throws FilmNotFoundException 
     */
    public int addScreening(String filmName, String time, int screenNum) throws FilmNotFoundException {
        //First, check the film exists
        getFilmByName(filmName); //If it doesn't exist, an exception will be thrown
        //Film exists, so add the screening
        int newScreeningIndex = screenings.length;
        ArrayList<Screening> temp = new ArrayList<>(Arrays.asList(screenings)); //Convert array to ArrayList for easy adding of new element
        temp.add(new Screening(newScreeningIndex, filmName, time, screenNum)); //Add the new seat element to the ArrayList
        screenings = temp.toArray(new Screening[0]); //Overwrite the seats array with the new array
        return newScreeningIndex;
    }
    
    /**
     * Method to check if a screen exists by screen number.
     * @param screenNum
     * @return True if screen exists, false if not
     */
    public boolean screenExists(int screenNum) {
        try {
            getScreenByNum(screenNum);
            return true;
        } catch (ScreenNotFoundException e) {
            return false;
        }
    }
    
    /**
     * Method to add a new screen to the system.
     * Checks the screen number doesn't already exist first.
     * @param screenNum
     * @throws ScreenAlreadyExistsException 
     */
    public void addScreen(int screenNum) throws ScreenAlreadyExistsException {
        if (!screenExists(screenNum)) {
            //If this error is caught, the screen doesn't already exist
            //Add the screen to the system
            ArrayList<Screen> temp = new ArrayList<>(Arrays.asList(screens)); //Convert array to ArrayList for easy adding of new element
            temp.add(new Screen(screenNum)); //Add the new screen element to the ArrayList
            screens = temp.toArray(new Screen[0]); //Overwrite the films array with the new array
        } else throw new ScreenAlreadyExistsException();
    }
    
     /**
     * Method to add a new review to the system.
     * @param filmName
     * @param content 
     */
    public void addReview(String customerName, String filmName, String content) {
        //Add the review to the system
        ArrayList<Review> temp = new ArrayList<>(Arrays.asList(reviews)); //Convert array to ArrayList for easy adding of new element
        temp.add(new Review(customerName, filmName, content)); //Add the new screen element to the ArrayList
        reviews = temp.toArray(new Review[0]); //Overwrite the films array with the new array
    }
    
    public void removeReview(Review review) {
        ArrayList<Review> temp = new ArrayList();
        for (Review r : reviews) {
            if (!r.getReviewContents().equals(r.getReviewContents())) {
                temp.add(r);
            }
        }
        reviews = temp.toArray(new Review[0]);
    }
    
    
    /**
     * Method to add a new newsletter to the system.
     * @param content 
     */
    public void addNewsletter(String content) {
        //Add the review to the system
        ArrayList<Newsletter> temp = new ArrayList<>(Arrays.asList(newsletters)); //Convert array to ArrayList for easy adding of new element
        temp.add(new Newsletter(content)); //Add the new screen element to the ArrayList
        newsletters = temp.toArray(new Newsletter[0]); //Overwrite the films array with the new array
    }
    
    
    /**
     * Method to add a new booking to the system.
     * @param screeningID
     * @param customerName
     * @param seatID
     * @return Returns the ID of the booking
     * @throws SeatNotAvaliableException
     * @throws ScreeningNotFoundException 
     */
    public int addBooking(int screeningID, String customerName, int seatID) throws SeatNotAvaliableException, ScreeningNotFoundException {
        int newBookingIndex = bookings.length;
        ArrayList<Booking> temp = new ArrayList<>(Arrays.asList(bookings)); //Convert array to ArrayList for easy adding of new element
        Booking newBooking = new Booking(newBookingIndex); //Create a new booking object
        newBooking.makeBooking(screeningID, customerName, seatID, this); //Adds the details to the booking, and books the seat
        temp.add(newBooking); //Add the new booking object to the ArrayList
        bookings = temp.toArray(new Booking[0]); //Overwrite the seats array with the new array
        return newBookingIndex;
    }
    
    /**
     * Method to check if an account exists by customer name
     * @param name
     * @return True if account exists, false if not
     */
    public boolean accountExists(String name) {
        try {
            getAccountByName(name);
        } catch (AccountNotFoundException e) {
            return false;
        }
        return true;
    }
    
    /**
     * Method to add an account to the system.
     * First checks the account doesn't exist.
     * @param name
     * @param pwd
     * @param ccNo
     * @throws AccountAlreadyExistsException
     * @throws InvalidPasswordException 
     * @throws invalidCardNumberException 
     */
    public void addAccount(String name, String pwd, String ccNo) throws AccountAlreadyExistsException, InvalidCardNumberException, InvalidPasswordException {
        //Add the account to the system
        ArrayList<Account> temp = new ArrayList<>(Arrays.asList(accounts)); //Convert array to ArrayList for easy adding of new element
        temp.add(new Account(name, pwd, ccNo, this)); //Add the new account element to the ArrayList
        accounts = temp.toArray(new Account[0]); //Overwrite the accounts array with the new array
    }
    
    public void removeAccount(Account account) {
        ArrayList<Account> temp = new ArrayList();
        for (Account a : accounts) {
            if (!a.getCustomerName().equals(account.getCustomerName())) {
                temp.add(a);
            }
        }
        accounts = temp.toArray(new Account[0]);
    }
    
    //Save and load data methods
    
    /**
     * Method to save the data to a file
     * @param saveFileName path and file name
     * @throws FileNotFoundException
     * @throws IOException 
     */
    public void saveData(String saveFileName) throws FileNotFoundException, IOException {
        
        // Write to disk with FileOutputStream
        FileOutputStream f_out = new FileOutputStream(saveFileName+".data");

        // Write object with ObjectOutputStream
        ObjectOutputStream obj_out = new ObjectOutputStream (f_out);

        // Write object out to disk
        obj_out.writeObject(this);
        
    }
    
    /**
     * Method to load data from a file
     * @param loadFileName path and file name
     * @throws FileNotFoundException
     * @throws IOException
     * @throws ClassNotFoundException 
     */
    public void loadData(String loadFileName) throws FileNotFoundException, IOException, ClassNotFoundException {
        
        // Read from disk using FileInputStream
        FileInputStream f_in = new FileInputStream(loadFileName+".data");

        // Read object using ObjectInputStream
        ObjectInputStream obj_in = new ObjectInputStream(f_in);

        // Read an object
        PictureMouse obj = (PictureMouse) obj_in.readObject();

        this.films = obj.films;
        this.accounts = obj.accounts;
        this.bookings = obj.bookings;
        this.reviews = obj.reviews;
        this.screenings = obj.screenings;
        this.screens = obj.screens;
        this.seats = obj.seats;
        this.newsletters = obj.newsletters;
          
    }
    
}
